﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NAntReflector.cs" company="NAntReflectionLib team">
//
// nant-cross-compile, an extension for NAnt for cross-compiling C/C++ applications.
// Copyright (C) 2012 nant-cross-compile team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/. 
// </copyright>
// <history>
//   <historyitem date="2013-02-24" change="Created file"/>
// </history>
// --------------------------------------------------------------------------------------------------------------------
namespace NAntReflectorLib
{
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Reflection;
  using System.Xml;
  using NAnt.Core;
  using NAnt.Core.Attributes;
  using NAntReflectorLib.NAntTypes;
  using XmlDocReflectorLib;
  using System.Threading;

  /// <summary>
  /// Class to get task, types, ... of a NAnt library.
  /// </summary>
  public class NAntReflector
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="NAntReflector"/> class.
    /// </summary>
    /// <param name="assemblyPath">The assembly path.</param>
    public NAntReflector(string assemblyPath)
    {    
      // Load assebmly
      Assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyPath));
      
      Thread taskThread = new Thread(this.GetAllTasks);
      Thread typeThread = new Thread(this.GetAllTypes);
      
      taskThread.Start();
      typeThread.Start();
      
      taskThread.Join();
      typeThread.Join();
    }

    private void GetAllTasks()
    {
      this.Tasks = this.GetNAntEntities<Task>();
    }

    private void GetAllTypes()
    {
      this.Types = this.GetNAntEntities<DataTypeBase>();
    }

    /// <summary>
    /// Gets the used assembly.
    /// </summary>
    /// <value>
    /// The used assembly.
    /// </value>
    public Assembly Assembly { get; private set; }

    /// <summary>
    /// Gets the tasks.
    /// </summary>
    /// <value>
    /// The tasks.
    /// </value>
    public List<NAntEntity> Tasks { get; private set; }

    /// <summary>
    /// Gets the types.
    /// </summary>
    /// <value>
    /// The types.
    /// </value>
    public List<NAntEntity> Types { get; private set; }

    /// <summary>
    /// Gets the XML doc member summary text.
    /// </summary>
    /// <param name="memberInfo">The member info.</param>
    /// <returns>A list containing all summary elements.</returns>
    private static List<XmlDocElement> GetXmlDocMemberSummaryText(MemberInfo memberInfo)
    {
      XmlDocNode node1 = XmlDocReflector.GetXmlDoc(memberInfo);
      if (node1 != null)
      {
        return GetNodeElements(node1.Summary);
      }
      else
      {
        return null;
      }
    }

    /// <summary>
    /// Gets the XML doc member remarks text.
    /// </summary>
    /// <param name="memberInfo">The member info.</param>
    /// <returns>
    /// A list containing all remark elements.
    /// </returns>
    private static List<XmlDocElement> GetXmlDocMemberRemarksText(MemberInfo memberInfo)
    {
      XmlDocNode node1 = XmlDocReflector.GetXmlDoc(memberInfo);
      if (node1 != null)
      {
        return GetNodeElements(node1.Remarks);
      }
      else
      {
        return null;
      }
    }

    /// <summary>
    /// Gets all node elements.
    /// </summary>
    /// <param name="node">The parent node.</param>
    /// <returns>A list containing all node elements.</returns>
    private static List<XmlDocElement> GetNodeElements(XmlNode node)
    {
      List<XmlDocElement> elements = new List<XmlDocElement>();
      if (node != null)
      {
        if (node.Value != null)
        {
          elements.Add(new XmlDocElement(node.Name, node.Value));
        }
        else
        {
          elements.Add(new XmlDocElement(node.Name, string.Empty));
        }

        if (node.ChildNodes.Count == 0)
        {
          if (node.Attributes != null)
          {
            foreach (XmlAttribute attribute in node.Attributes)
            {
              elements.Add(new XmlDocElement(attribute.Name, attribute.Value));
            }
          }
        }
        else
        {
          foreach (XmlNode childNode in node)
          {
            elements.AddRange(GetNodeElements(childNode));
          }
        }
      }

      return elements;
    }

    /// <summary>
    /// Gets the NAnt entities with the specified base type from the passed assembly..
    /// </summary>
    /// <typeparam name="TEntity">The base type of the entity.</typeparam>
    /// <param name="containingAssembly">The containing assembly.</param>
    /// <returns>A list containing the entities.</returns>
    private List<NAntEntity> GetNAntEntities<TEntity>()
    {
      List<NAntEntity> entities = new List<NAntEntity>();
      Type[] assemblyTypes = this.Assembly.GetExportedTypes();
      foreach (Type entityType in assemblyTypes)
      {
        if (entityType.IsSubclassOf(typeof(TEntity)))
        {
          NAntEntity entity = new NAntEntity();
          entities.Add(entity);

          entity.Remarks = GetXmlDocMemberRemarksText(entityType);
          entity.Summary = GetXmlDocMemberSummaryText(entityType);
          entity.Type = entityType;
          entity.IsAbstract = entityType.IsAbstract;

          // Get NAnt entity name
          object[] taskAttributes = entityType.GetCustomAttributes(typeof(ElementNameAttribute), false);
          if (taskAttributes.Length > 0)
          {
            entity.Name = (taskAttributes[0] as ElementNameAttribute).Name;
          }

          PropertyInfo[] properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
          foreach (PropertyInfo p in properties)
          {
            // Get parameters of entity
            entity.Parameters.AddRange(this.CreateNAntEntityParameters<TaskAttributeAttribute>(p));

            // Get nested elements of entity
            entity.NestedElements.AddRange(this.CreateNAntEntityParameters<BuildElementAttribute>(p));            
          }
        }
      }

      return entities;
    }

    /// <summary>
    /// Creates the NAnt entity parameters.
    /// </summary>
    /// <typeparam name="TEntityAttributeType">The type of the entity attribute type.</typeparam>
    /// <param name="p">The property which represents the parameter.</param>
    /// <returns>A list with the NAnt entity parameters.</returns>
    private List<NAntEntityParameter> CreateNAntEntityParameters<TEntityAttributeType>(PropertyInfo p) where TEntityAttributeType : class
    {
      List<NAntEntityParameter> parameters = new List<NAntEntityParameter>();
      foreach (object attribute in p.GetCustomAttributes(typeof(TEntityAttributeType), true))
      {
        NAntEntityParameter parameter = new NAntEntityParameter();
        parameters.Add(parameter);

        TEntityAttributeType docAttribute = attribute as TEntityAttributeType;
        parameter.Remarks = GetXmlDocMemberRemarksText(p);
        parameter.Summary = GetXmlDocMemberSummaryText(p);

        parameter.ParameterType = p.PropertyType;
        parameter.Name = p.Name;

        // Reflection must be used because TaskAttributeAttribute and BuildElementAttribute don't use a base class
        // which defines the properties "Name" and "Required"
        parameter.NAntName = (string)docAttribute.GetType().GetProperty("Name").GetValue(docAttribute, null);
        parameter.Required = (bool)docAttribute.GetType().GetProperty("Required").GetValue(docAttribute, null);
      }

      return parameters;
    }
  }
}
